﻿//------------------------------------------------------------------------------
// Copyright © FRA & FV 2014
// All rights reserved
//------------------------------------------------------------------------------
// Binary Serialization SDK
//
// SVN revision information:
//   $Revision: 90 $
//------------------------------------------------------------------------------
using System;
using System.IO;
using System.Text;

namespace FRAFV.Binary.Serialization
{
	/// <summary>Reads primitive data types as binary values with specific byte order and in a specific encoding.</summary>
	public class BinReader : BinaryReader
	{
		private byte[] buffer = new byte[8];

		/// <summary>Indicates the byte order in which data is stored in the supplied stream.</summary>
		/// <returns>The byte order in the supplied stream.</returns>
		public bool IsLittleEndian { get; set; }

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.BinReader" /> class
		///   based on the supplied stream and using <see cref="T:System.Text.UTF8Encoding" />.</summary>
		/// <param name="input">The supplied stream.</param>
		/// <exception cref="T:System.ArgumentException">The stream does not support reading, the stream is null, or
		///   the stream is already closed. </exception>
		public BinReader(Stream input)
			: base(input)
		{
			this.IsLittleEndian = BitConverter.IsLittleEndian;
		}

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.BinReader" /> class
		///   based on the supplied stream and a specific character encoding.</summary>
		/// <param name="input">The supplied stream.</param>
		/// <param name="encoding">The character encoding. </param>
		/// <exception cref="T:System.ArgumentException">The stream does not support reading, the stream is null, or
		///   the stream is already closed. </exception>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="encoding" /> is null. </exception>
		public BinReader(Stream input, Encoding encoding)
			: base(input, encoding)
		{
			this.IsLittleEndian = BitConverter.IsLittleEndian;
		}

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.BinReader" /> class
		///   based on the supplied stream with specific byte order and using <see cref="T:System.Text.UTF8Encoding" />.</summary>
		/// <param name="input">The supplied stream.</param>
		/// <param name="littleEndian">true to use the little-endian byte order (most significant byte first), or false
		///   to use the big-endian byte order (least significant byte first).</param>
		/// <exception cref="T:System.ArgumentException">The stream does not support reading, the stream is null, or
		///   the stream is already closed. </exception>
		public BinReader(Stream input, bool littleEndian)
			: base(input)
		{
			this.IsLittleEndian = littleEndian;
		}

		/// <summary>Initializes a new instance of the <see cref="T:FRAFV.Binary.Serialization.BinReader" /> class
		///   based on the supplied stream with specific byte order and a specific character encoding.</summary>
		/// <param name="input">The supplied stream.</param>
		/// <param name="encoding">The character encoding. </param>
		/// <param name="littleEndian">true to use the little-endian byte order (most significant byte first), or false
		///   to use the big-endian byte order (least significant byte first).</param>
		/// <exception cref="T:System.ArgumentException">The stream does not support reading, the stream is null, or
		///   the stream is already closed. </exception>
		/// <exception cref="T:System.ArgumentNullException">
		///   <paramref name="encoding" /> is null. </exception>
		public BinReader(Stream input, Encoding encoding, bool littleEndian)
			: base(input, encoding)
		{
			this.IsLittleEndian = littleEndian;
		}

		private byte[] Buffer(int numBytes)
		{
			int total = 0;
			if (this.BaseStream == null)
				throw new ObjectDisposedException(null);
			if (numBytes == 1)
			{
				int read = BaseStream.ReadByte();
				if (read == -1)
					throw new EndOfStreamException();
				this.buffer[0] = (byte)read;
				return this.buffer;
			}
			do
			{
				int read = BaseStream.Read(buffer, total, numBytes - total);
				if (read == 0)
					throw new EndOfStreamException();
				total += read;
			}
			while (total < numBytes);
			return this.buffer;
		}

		/// <summary>Reads a 2-byte signed integer from the current stream and advances the current position of
		///   the stream by two bytes.</summary>
		/// <returns>A 2-byte signed integer read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public override short ReadInt16()
		{
			return BinConverter.InternalToInt16(IsLittleEndian, Buffer(2));
		}

		/// <summary>Reads a 3-byte signed integer from the current stream and advances the current position of
		///   the stream by three bytes.</summary>
		/// <returns>A 3-byte signed integer read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public virtual int ReadInt24()
		{
			return BinConverter.InternalToInt24(IsLittleEndian, Buffer(3));
		}

		/// <summary>Reads a 4-byte signed integer from the current stream and advances the current position of
		///   the stream by four bytes.</summary>
		/// <returns>A 4-byte signed integer read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public override int ReadInt32()
		{
			return BinConverter.InternalToInt32(IsLittleEndian, Buffer(4));
		}

		/// <summary>Reads an 8-byte signed integer from the current stream and advances the current position of
		///   the stream by eight bytes.</summary>
		/// <returns>An 8-byte signed integer read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public override long ReadInt64()
		{
			return (long)BinConverter.InternalToUInt64(IsLittleEndian, Buffer(8));
		}

		/// <summary>Reads a 2-byte unsigned integer from the current stream and advances the current position of
		///   the stream by two bytes.</summary>
		/// <returns>A 2-byte unsigned integer read from this stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		/// <filterpriority>2</filterpriority>
		public override ushort ReadUInt16()
		{
			return (ushort)BinConverter.InternalToInt16(IsLittleEndian, Buffer(2));
		}

		/// <summary>Reads a 3-byte unsigned integer from the current stream and advances the current position of
		///   the stream by three bytes.</summary>
		/// <returns>A 3-byte unsigned integer read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public virtual int ReadUInt24()
		{
			return BinConverter.InternalToUInt24(IsLittleEndian, Buffer(3));
		}

		/// <summary>Reads a 4-byte unsigned integer from the current stream and advances the current position of
		///   the stream by four bytes.</summary>
		/// <returns>A 4-byte unsigned integer read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public override uint ReadUInt32()
		{
			return (uint)BinConverter.InternalToInt32(IsLittleEndian, Buffer(4));
		}

		/// <summary>Reads an 8-byte signed integer from the current stream and advances the current position of
		///   the stream by eight bytes.</summary>
		/// <returns>An 8-byte signed integer read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public override ulong ReadUInt64()
		{
			return BinConverter.InternalToUInt64(IsLittleEndian, Buffer(8));
		}

		/// <summary>Reads a 4-byte floating-point value from the current stream and advances the current position of
		///   the stream by four bytes.</summary>
		/// <returns>A 4-byte floating-point value read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public override float ReadSingle()
		{
			return BinConverter.InternalToSingle(IsLittleEndian, Buffer(4));
		}

		/// <summary>Reads an 8-byte floating-point value from the current stream and advances the current position of
		///   the stream by eight bytes.</summary>
		/// <returns>An 8-byte floating-point value read from the current stream.</returns>
		/// <exception cref="T:System.IO.EndOfStreamException">The end of the stream is reached. </exception>
		/// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
		/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
		public override double ReadDouble()
		{
			return BinConverter.InternalToDouble(IsLittleEndian, Buffer(8));
		}
	}
}
